/////////////////////////////////////////////////////////////////////////////
// This file is part of the "Java-DAP" project, a Java implementation
// of the OPeNDAP Data Access Protocol.
//
// Copyright (c) 2007 OPeNDAP, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
/////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections;
using System.IO;
using NDODS.DAP.Exceptions;
using NDODS.DAP.Primitives;
using NDODS.DAP.Server;
using NDODS.DAP.XMLParser;

namespace NDODS.DAP.BaseTypes
{
    /// <summary> This class is used to hold arrays of other OPeNDAP data. The elements of the
    /// array can be simple or compound data types. There is no limit on the
    /// number of dimensions an array can have, or on the size of each dimension.
    /// <p/>
    /// If desired, the user can give each dimension of an array a name. You can,
    /// for example, have a 360x180 array of temperatures, covering the whole
    /// globe with one-degree squares. In this case, you could name the first
    /// dimension "Longitude" and the second dimension "Latitude". This can
    /// help prevent a great deal of confusion.
    /// <p/>
    /// The <code>DArray</code> is used as part of the <code>DGrid</code> class,
    /// where the dimension names are crucial to its structure. The dimension names
    /// correspond to "Map" vectors, holding the actual values for that column of
    /// the array.
    /// <p/>
    /// Each array dimension carries with it its own projection information. The
    /// projection inforamtion takes the form of three integers: the start, stop,
    /// and stride values. This is clearest with an example. Consider a
    /// one-dimensional array 10 elements long. If the start value of the
    /// dimension constraint is 3, then the constrained array appears to be seven
    /// elements long. If the stop value is changed to 7, then the array appears
    /// to be five elements long. If the stride is changed to two, the array will
    /// appear to be 3 elements long. Array constraints are written as
    /// <code>[start:stride:stop]</code>.
    /// <p/>
    /// <code><pre>
    /// A = [1 2 3 4 5 6 7 8 9 10]
    /// A[3::] = [4 5 6 7 8 9 10]
    /// A[3::7] = [4 5 6 7 8]
    /// A[3:2:7] = [4 6 8]
    /// A[0:3:9] = [1 4 7 10]
    /// </pre></code>
    /// <p/>
    /// NB: OPeNDAP uses zero-based indexing.
    /// 
    /// </summary>
    /// <author>  jehamby
    /// </author>
    /// <version>  $Revision: 15901 $
    /// </version>
    /// <seealso cref="DGrid">
    /// </seealso>
    /// <seealso cref="DVector">
    /// </seealso>
    /// <seealso cref="BaseType">
    /// </seealso>
    [Serializable]
    public class DArray : DVector, ICloneable
    {
        /// <summary> Returns the OPeNDAP type name of the class instance as a <code>String</code>.
        /// 
        /// </summary>
        /// <returns> the OPeNDAP type name of the class instance as a <code>String</code>.
        /// </returns>
        public override String TypeName {
            get { return "Array"; }
        }

        /// <summary> Returns an <code>Enumeration</code> of <code>DArrayDimension</code>s
        /// in this array.
        /// 
        /// </summary>
        /// <returns> an <code>Enumeration</code> of <code>DArrayDimension</code>s
        /// in this array.
        /// </returns>
        public virtual IEnumerator Dimensions {
            get { return dimVector.GetEnumerator(); }
        }

        /// <summary> Returns the <code>DArrayDimension</code> object for
        /// the first dimension.
        /// </summary>
        public virtual DArrayDimension FirstDimension {
            get { return ((DArrayDimension) dimVector[0]); }
        }

        /// <summary> A Vector of DArrayDimension information (i.e. the shape)</summary>
        private ArrayList dimVector;

        /// <summary> Constructs a new <code>DArray</code>.</summary>
        public DArray() : this(null) {}

        /// <summary> Constructs a new <code>DArray</code> with name <code>n</code>.
        /// 
        /// </summary>
        /// <param name="n">the name of the variable.
        /// </param>
        public DArray(String n) : base(n) {
            dimVector = ArrayList.Synchronized(new ArrayList(10));
        }

        /// <summary> Returns a clone of this <code>DArray</code>.  A deep copy is performed
        /// on all data inside the variable.
        /// 
        /// </summary>
        /// <returns> a clone of this <code>DArray</code>.
        /// </returns>
        public override Object Clone() {
            DArray a = (DArray) base.Clone();
            a.dimVector = ArrayList.Synchronized(new ArrayList(10));
            for (int i = 0; i < dimVector.Count; i++) {
                DArrayDimension d = (DArrayDimension) dimVector[i];
                a.dimVector.Add(d.Clone());
            }
            return a;
        }

        /// <summary> Checks for internal consistency.  For <code>DArray</code>, verify that
        /// the dimension vector is not empty.
        /// 
        /// </summary>
        /// <param name="all">for complex constructor types, this flag indicates whether to
        /// check the semantics of the member variables, too.
        /// </param>
        /// <throws>  BadSemanticsException if semantics are bad, explains why. </throws>
        /// <seealso cref="BaseType.CheckSemantics(bool)">
        /// </seealso>
        public override void CheckSemantics(bool all) {
            base.CheckSemantics(all);

            if ((dimVector.Count == 0))
                throw new BadSemanticsException("An array variable must have dimensions");
        }


        /// <summary> Write the variable's declaration in a C-style syntax. This
        /// function is used to create textual representation of the Data
        /// Descriptor Structure (DDS).  See <em>The OPeNDAP User Manual</em> for
        /// information about this structure.
        /// 
        /// </summary>
        /// <param name="os">        The <code>StreamWriter</code> on which to Print the
        /// declaration.
        /// </param>
        /// <param name="space">     Each line of the declaration will begin with the
        /// characters in this string.  Usually used for leading spaces.
        /// </param>
        /// <param name="print_semi">a bool value indicating whether to Print a
        /// semicolon at the end of the declaration.
        /// </param>
        /// <seealso cref="BaseType.PrintDeclaration(StreamWriter, String, bool)">
        /// </seealso>
        public override void PrintDeclaration(StreamWriter os, String space, bool print_semi, bool constrained) {
            // BEWARE! Since PrintDeclaration()is (multiplely) overloaded in BaseType and
            // all of the different signatures of PrintDeclaration() in BaseType lead to
            // one signature, we must be careful to override that SAME signature
            // here. That way all calls to PrintDeclaration() for this object lead to
            // this implementation.

            //os.println("DArray.PrintDeclaration()");

            PrimitiveVector.printDecl(os, space, false, constrained);
            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            for (IEnumerator e = dimVector.GetEnumerator(); e.MoveNext();) {
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                DArrayDimension d = (DArrayDimension) e.Current;
                os.Write("[");
                if (d.Name != null)
                    os.Write(d.Name + " = ");
                os.Write(d.Size + "]");
            }
            if (print_semi) {
                //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
                os.WriteLine(";");
            }
        }

        /// <summary> Prints the value of the variable, with its declaration.  This
        /// function is primarily intended for debugging OPeNDAP applications and
        /// text-based clients such as geturl.
        /// 
        /// </summary>
        /// <param name="pw">          the <code>StreamWriter</code> on which to Print the value.
        /// </param>
        /// <param name="space">       this value is passed to the <code>PrintDeclaration</code> method,
        /// and controls the leading spaces of the output.
        /// </param>
        /// <param name="Print_decl_p">a bool value controlling whether the
        /// variable declaration is printed as well as the value.
        /// </param>
        /// <seealso cref="BaseType.printVal(StreamWriter, String, bool)">
        /// </seealso>
        public override void PrintValue(StreamWriter pw, String space, bool print_decl_p) {
            // Print the declaration if Print decl is true.
            // for each dimension,
            //   for each element,
            //     Print the array given its shape, number of dimensions.
            // Add the `;'

            if (print_decl_p) {
                PrintDeclaration(pw, space, false);
                pw.Write(" = ");
            }

            int dims = numDimensions();
            int[] shape = new int[dims];
            int i = 0;
            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            for (IEnumerator e = dimVector.GetEnumerator(); e.MoveNext();) {
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                DArrayDimension d = (DArrayDimension) e.Current;
                shape[i++] = d.Size;
            }

            printArray(pw, 0, dims, shape, 0);

            if (print_decl_p) {
                //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
                pw.WriteLine(";");
            }

            pw.Flush();
        }

        /// <summary> Print an array. This is a private member function.
        /// 
        /// </summary>
        /// <param name="os">    is the stream used for writing
        /// </param>
        /// <param name="index"> is the index of VEC to start printing
        /// </param>
        /// <param name="dims">  is the number of dimensions in the array
        /// </param>
        /// <param name="shape"> holds the size of the dimensions of the array.
        /// </param>
        /// <param name="offset">holds the current offset into the dimension array.
        /// </param>
        /// <returns> the number of elements written.
        /// </returns>
        private int printArray(StreamWriter os, int index, int dims, int[] shape, int offset) {
            if (dims == 1) {
                os.Write("{");
                for (int i = 0; i < shape[offset] - 1; i++) {
                    PrimitiveVector.printSingleVal(os, index++);
                    os.Write(", ");
                }
                PrimitiveVector.printSingleVal(os, index++);
                os.Write("}");
                return index;
            }
            else {
                os.Write("{");
                for (int i = 0; i < shape[offset] - 1; i++) {
                    index = printArray(os, index, dims - 1, shape, offset + 1);
                    os.Write(",");
                }
                index = printArray(os, index, dims - 1, shape, offset + 1);
                os.Write("}");
                return index;
            }
        }

        /// <summary> Given a size and a name, this function adds a dimension to the
        /// array.  For example, if the <code>DArray</code> is already 10 elements
        /// long, calling <code>appendDim</code> with a size of 5 will transform the
        /// array into a 10x5 matrix.  Calling it again with a size of 2 will
        /// create a 10x5x2 array, and so on.
        /// 
        /// </summary>
        /// <param name="size">the size of the desired new dimension.
        /// </param>
        /// <param name="name">the name of the new dimension.
        /// </param>
        public virtual void appendDim(int size, String name) {
            DArrayDimension newDim = new DArrayDimension(size, name);
            dimVector.Add(newDim);
        }

        /// <summary> Given a size and a name, this function adds a dimension to the
        /// array.  For example, if the <code>DArray</code> is already 10 elements
        /// long, calling <code>appendDim</code> with a size of 5 will transform the
        /// array into a 10x5 matrix.  Calling it again with a size of 2 will
        /// create a 10x5x2 array, and so on.
        /// 
        /// </summary>
        /// <param name="size">the size of the desired new dimension.
        /// </param>
        /// <param name="name">the name of the new dimension.
        /// </param>
        public virtual void appendDim(int size, String name, bool decodeName) {
            DArrayDimension newDim = new DArrayDimension(size, name, decodeName);
            dimVector.Add(newDim);
        }

        /// <summary> Add a dimension to the array.  Same as <code>appendDim(size, null)</code>.
        /// 
        /// </summary>
        /// <param name="size">the size of the desired new dimension.
        /// </param>
        /// <seealso cref="DArray.appendDim(int, String)">
        /// </seealso>
        public virtual void appendDim(int size) {
            appendDim(size, null);
        }

        /// <summary> Returns the number of dimensions in this array.
        /// 
        /// </summary>
        /// <returns> the number of dimensions in this array.
        /// </returns>
        public int numDimensions() {
            return dimVector.Count;
        }


        /// <summary> Returns the <code>DArrayDimension</code> object for
        /// the dimension requested. It makes sure that the dimension requested
        /// exists.
        /// </summary>
        public virtual DArrayDimension getDimension(int dimension) {
            // QC the passed dimension
            if (dimension < dimVector.Count)
                return ((DArrayDimension) dimVector[dimension]);
            else
                throw new InvalidParameterException(
                    "DArray.getDimension(): Bad dimension request: dimension > # of dimensions");
        }


        /// <summary> </summary>
        /// <param name="pw">
        /// </param>
        /// <param name="pad">
        /// </param>
        /// <param name="constrained">
        /// </param>
        /// <opendap.ddx.experimental>  </opendap.ddx.experimental>
        public override void PrintXML(StreamWriter pw, String pad, bool constrained) {
            pw.Write(pad + "<Array");
            if (Name != null) {
                pw.Write(" name=\"" + DDSXMLParser.normalizeToXML(ClearName) + "\"");
            }
            //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
            pw.WriteLine(">");


            printXMLcore(pw, pad, constrained);

            //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
            pw.WriteLine(pad + "</Array>");
        }


        /// <summary> </summary>
        /// <param name="pw">
        /// </param>
        /// <param name="pad">
        /// </param>
        /// <param name="constrained">
        /// </param>
        /// <opendap.ddx.experimental>  </opendap.ddx.experimental>
        public virtual void printAsMapXML(StreamWriter pw, String pad, bool constrained) {
            pw.Write(pad + "<Map");
            if (Name != null) {
                pw.Write(" name=\"" + DDSXMLParser.normalizeToXML(ClearName) + "\"");
            }
            //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
            pw.WriteLine(">");

            printXMLcore(pw, pad, constrained);

            //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
            pw.WriteLine(pad + "</Map>");
        }


        /// <summary> </summary>
        /// <param name="pw">
        /// </param>
        /// <param name="pad">
        /// </param>
        /// <param name="constrained">
        /// </param>
        /// <opendap.ddx.experimental>  </opendap.ddx.experimental>
        private void printXMLcore(StreamWriter pw, String pad, bool constrained) {
            IEnumerator e = AttributeNames.GetEnumerator();
            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            while (e.MoveNext()) {
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                String aName = (String) e.Current;

                try {
                    Attribute a = GetAttribute(aName);
                    a.printXML(pw, pad + "\t", constrained);
                }
                catch (NoSuchAttributeException nsae) {}
            }


            BaseType bt = null;

            PrimitiveVector pv = PrimitiveVector;
            // *** Nathan, can we get rid of this 'if?' 05/15/03 jhrg
            if (pv is BaseTypePrimitiveVector) {
                bt = pv.Template;
            }
            else {
                bt = pv.Template;
            }

            String nameCache = bt.Name;

            bt.Name = null;

            bt.PrintXML(pw, pad + "\t", constrained);

            bt.Name = nameCache;

            IEnumerator dae = Dimensions;
            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            while (dae.MoveNext()) {
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                DArrayDimension dad = (DArrayDimension) dae.Current;
                int size = dad.Size;
                String name = dad.Name;
                if (name == null) {
                    //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
                    pw.WriteLine(pad + "\t" + "<dimension size=\"" + size + "\"/>");
                }
                else {
                    //UPGRADE_TODO: Method 'java.io.StreamWriter.println' was converted to 'System.IO.TextWriter.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioStreamWriterprintln_javalangString'"
                    pw.WriteLine(pad + "\t" + "<dimension name=\"" + DDSXMLParser.normalizeToXML(name) + "\" size=\"" +
                                 size + "\"/>");
                }
            }
        }
    }
}